
#ifndef VECTOR2_H_
#define VECTOR2_H_

#include "libmath.h"

namespace p2 {

template<typename Real>
class Vector2
{
public:
	typedef std::list<Vector2<Real>, boost::fast_pool_allocator<Vector2<Real> > > List;

public:
	inline Vector2(); // uninitialized
	inline Vector2(Vector2<Real> const& v);
	inline Vector2(Real x, Real y);
	inline Vector2(Real* v);
	inline ~Vector2();

	/*
	 * access
	 */
	inline Real X() const;
	inline Real& X();
	inline Real Y() const;
	inline Real& Y();
	/*
	 * operators
	 */
	inline Real const& operator[](int i) const;
	inline Real& operator[](int i);
	inline Vector2<Real>& operator=(const Vector2<Real>& v);
	inline Vector2<Real> operator+(const Vector2<Real>& v) const;
	inline Vector2<Real> operator-(const Vector2<Real>& v) const;
	inline Vector2<Real> operator*(Real scalar) const;
	inline Vector2<Real> operator/(Real scalar) const;
	inline Vector2<Real> operator-() const;
	inline Vector2<Real>& operator+=(const Vector2<Real>& v);
	inline Vector2<Real>& operator-=(const Vector2<Real>& v);
	inline Vector2<Real>& operator*=(Real scalar);
	inline Vector2<Real>& operator/=(Real scalar);
	/*
	 * operations
	 */
	inline Real length() const;
	static inline Real length(Vector2<Real> v);
	inline Real invLength() const;
	inline Real squaredLength() const;
	static inline Real squaredLength(Vector2<real> v);
	inline Real dot(Vector2<Real> v) const;
	static inline Real dot(Vector2<Real> v1, Vector2<Real> v2);
	static inline Vector2<Real> normalise(Vector2<Real> v);
	inline void normalise();
	static inline Vector2<Real> projectPointOntoEdgeClamped(Vector2<Real> p, Vector2<Real> e0, Vector2<Real> e1);
	static inline Vector2<Real> projectPointOntoEdgeUnclamped(Vector2<Real> p, Vector2<Real> e0, Vector2<Real> e1);
	inline Real squaredDistanceToEdgeClamped(Vector2<Real> p, Vector2<Real> e0, Vector2<Real> e1, Vector2<Real>& closest);
	inline Real squaredDistanceToEdgeUnclamped(Vector2<Real> p, Vector2<Real> e0, Vector2<Real> e1, Vector2<Real>& closest);
	static inline Vector2<Real> makeNormal(Vector2<Real> const& v0, Vector2<Real> const& v1);
	inline Vector2<Real> clamp(Vector2<Real> min, Vector2<Real> max);
	static inline Vector2<Real> clamp(Vector2<Real> v, Real len);
	inline Vector2<Real> perp() const;
	inline Vector2<Real> unitPerp() const;
	/*
	 * constants
	 */
	static const Vector2 ZERO; // (0,0)
	static const Vector2 UNIT_X; // (1,0)
	static const Vector2 UNIT_Y; // (0,1)
	static const Vector2 ONE; // (1,1)

protected:
	Real _t[2];
};

template<typename Real>
inline Vector2<Real> operator*(Real scalar, const Vector2<Real>& v);

template<typename Real>
Vector2<Real>::Vector2()
{
}

template<typename Real>
Vector2<Real>::Vector2(const Vector2<Real>& v)
{
	_t[0] = v._t[0];
	_t[1] = v._t[1];
}

template<typename Real>
Vector2<Real>::Vector2(Real x, Real y)
{
	_t[0] = x;
	_t[1] = y;
}
template<typename Real>
Vector2<Real>::Vector2(Real* v)
{
	_t[0] = v[0];
	_t[1] = v[1];
}

template<typename Real>
Vector2<Real>::~Vector2()
{
}

template<typename Real>
inline Real Vector2<Real>::X() const
{
	return _t[0];
}

template<typename Real>
inline Real& Vector2<Real>::X()
{
	return _t[0];
}

template<typename Real>
inline Real Vector2<Real>::Y() const
{
	return _t[1];
}

template<typename Real>
inline Real& Vector2<Real>::Y()
{
	return _t[1];
}

template<typename Real>
inline Vector2<Real>& Vector2<Real>::operator=(const Vector2<Real>& v)
{
	_t[0] = v._t[0];
	_t[1] = v._t[1];
	return *this;
}

template<typename Real>
inline Real const& Vector2<Real>::operator[](int i) const
{
	return _t[i];
}

template<typename Real>
inline Real& Vector2<Real>::operator[](int i)
{
	return _t[i];
}

template<typename Real>
inline Vector2<Real> Vector2<Real>::operator+(const Vector2<Real>& v) const
{
	return Vector2<Real>(_t[0] + v._t[0], _t[1] + v._t[1]);
}

template<typename Real>
inline Vector2<Real> Vector2<Real>::operator-(const Vector2<Real>& v) const
{
	return Vector2<Real>(_t[0] - v._t[0], _t[1] - v._t[1]);
}

template<typename Real>
inline Vector2<Real> Vector2<Real>::operator*(Real scalar) const
{
	return Vector2<Real>(scalar * _t[0], scalar * _t[1]);
}

template<typename Real>
inline Vector2<Real> Vector2<Real>::operator/(Real scalar) const
{
	Vector2<Real> result;

	if (scalar != (Real) 0) {
		Real invScalar = ((Real) 1) / scalar;
		result._t[0] = invScalar * _t[0];
		result._t[1] = invScalar * _t[1];
	} else {
		result._t[0] = Math<Real>::MAX_REAL;
		result._t[1] = Math<Real>::MAX_REAL;
	}

	return result;
}

template<typename Real>
inline Vector2<Real> Vector2<Real>::operator-() const
{
	return Vector2<Real>(-_t[0], -_t[1]);
}

template<typename Real>
inline Vector2<Real>& Vector2<Real>::operator+=(const Vector2<Real>& v)
{
	_t[0] += v._t[0];
	_t[1] += v._t[1];
	return *this;
}

template<typename Real>
inline Vector2<Real>& Vector2<Real>::operator-=(const Vector2<Real>& v)
{
	_t[0] -= v._t[0];
	_t[1] -= v._t[1];
	return *this;
}

template<typename Real>
inline Vector2<Real>& Vector2<Real>::operator*=(Real scalar)
{
	_t[0] *= scalar;
	_t[1] *= scalar;
	return *this;
}

template<typename Real>
inline Vector2<Real>& Vector2<Real>::operator/=(Real scalar)
{
	if (scalar != (Real) 0) {
		Real invScalar = ((Real) 1) / scalar;
		_t[0] *= invScalar;
		_t[1] *= invScalar;
	} else {
		_t[0] *= Math<Real>::MAX_REAL;
		_t[1] *= Math<Real>::MAX_REAL;
	}

	return *this;
}

template<typename Real>
inline Real Vector2<Real>::length() const
{
	return Math<Real>::Sqrt(_t[0] * _t[0] + _t[1] * _t[1]);
}

template<typename Real>
inline Real Vector2<Real>::length(Vector2<Real> v)
{
	return Math<Real>::Sqrt(v._t[0] * v._t[0] + v._t[1] * v._t[1]);
}

template<typename Real>
inline Real Vector2<Real>::invLength() const
{
	return Math<Real>::InvSqrt(_t[0] * _t[0] + _t[1] * _t[1]);
}

template<typename Real>
inline Real Vector2<Real>::squaredLength() const
{
	return (_t[0] * _t[0] + _t[1] * _t[1]);
}

template<typename Real>
inline Real Vector2<Real>::squaredLength(Vector2<real> v)
{
	return dot(v, v);
}

template<typename Real>
inline Real Vector2<Real>::dot(Vector2<Real> v) const
{
	return (_t[0] * v._t[0] + _t[1] * v._t[1]);
}

template<typename Real>
inline Real Vector2<Real>::dot(Vector2<Real> v1, Vector2<Real> v2)
{
	return (v1._t[0] * v2._t[0] + v1._t[1] * v2._t[1]);
}

template<typename Real>
inline void Vector2<Real>::normalise()
{
	Real len2 = squaredLength();
	if (len2 > Math<Real>::ZERO_TOLERANCE2) {
		Real invLen = Math<Real>::InvSqrt(len2);
		_t[0] *= invLen;
		_t[1] *= invLen;
	} else {
		_t[0] = (Real) 0;
		_t[1] = (Real) 0;
	}
}

template<typename Real>
inline Vector2<Real> Vector2<Real>::normalise(Vector2<Real> v)
{
	Real len2 = v.squaredLength();
	if (len2 > Math<Real>::ZERO_TOLERANCE2) {
		Real invLen = Math<Real>::InvSqrt(len2);
		v._t[0] *= invLen;
		v._t[1] *= invLen;
	} else {
		v._t[0] = (Real) 0;
		v._t[1] = (Real) 0;
	}
	return v;
}

template<typename Real>
inline Vector2<Real> Vector2<Real>::projectPointOntoEdgeClamped(Vector2<Real> p, Vector2<Real> e0, Vector2<Real> e1)
{
	Vector2<Real> e = e1 - e0;
	Real t = e.dot(p - e0) / Vector2<real>::squaredLength(e);
	t = Math<Real>::Clamp(t, 0.f, 1.f);
	return e0 + e * t;
}

template<typename Real>
inline Vector2<Real> Vector2<Real>::projectPointOntoEdgeUnclamped(Vector2<Real> p, Vector2<Real> e0, Vector2<Real> e1)
{
	Vector2<Real> e = e1 - e0;
	Real t = e.dot(p - e0) / Vector2<real>::squaredLength(e);
	return e0 + e * t;
}

template<typename Real>
inline Real Vector2<Real>::squaredDistanceToEdgeClamped(Vector2<Real> p, Vector2<Real> e0, Vector2<Real> e1, Vector2<Real>& closest)
{
	closest = projectPointOntoEdgeClamped(p, e0, e1);
	return (closest - p).squaredLength();
}

template<typename Real>
inline Real Vector2<Real>::squaredDistanceToEdgeUnclamped(Vector2<Real> p, Vector2<Real> e0, Vector2<Real> e1, Vector2<Real>& closest)
{
	closest = projectPointOntoEdgeUnclamped(p, e0, e1);
	return (closest - p).squaredLength();
}

template<typename Real>
inline Vector2<Real> Vector2<Real>::makeNormal(Vector2<Real> const& v0, Vector2<Real> const& v1)
{
	Vector2<Real> n((v1 - v0).perp());
	n.normalise();
	return n;
}

template<typename Real>
inline Vector2<Real> Vector2<Real>::clamp(Vector2<Real> min, Vector2<Real> max)
{
	return Vector2<Real>(
		Math<Real>::Max(Math<Real>::Min(X(), max.X()), min.X()),
		Math<Real>::Max(Math<Real>::Min(Y(), max.Y()), min.Y())
    );
}

template<typename Real>
inline Vector2<Real> Vector2<Real>::clamp(Vector2<Real> v, Real len)
{
	return (Vector2<Real>::dot(v, v) > len * len) ? (Vector2<Real>::normalise(v) * len) : v;
}

template<typename Real>
inline Vector2<Real> Vector2<Real>::perp() const
{
	return Vector2<Real>(_t[1], -_t[0]);
}

template<typename Real>
inline Vector2<Real> Vector2<Real>::unitPerp() const
{
	Vector2<Real> perp(_t[1], -_t[0]);
	perp.normalise();
	return perp;
}

template<typename Real>
inline Vector2<Real> operator*(Real scalar, const Vector2<Real>& v)
{
	return Vector2<Real>(scalar * v[0], scalar * v[1]);
}

}

#endif	//VECTOR2_H_
